local base    = _G
local string  = string 
local os      = os
local redis = redis
local call  = redis.call
local pcall = redis.pcall
module("weibo_redis",package.seeall)

local commands = {
    "append",            "auth",              "bgrewriteaof",
    "bgsave",            "blpop",             "brpop",
    "brpoplpush",        "config",            "dbsize",
    "debug",             "decr",              "decrby",
    "del",               "discard",           "echo",
    "eval",              "exec",              "exists",
    "expire",            "expireat",          "flushall",
    "flushdb",           "get",               "getbit",
    "getrange",          "getset",            "hdel",
    "hexists",           "hget",              "hgetall",
    "hincrby",           "hkeys",             "hlen",
    "hmget",             --[[ "hmset", ]]     "hset",
    "hsetnx",            "hvals",             "incr",
    "incrby",            "info",              "keys",
    "lastsave",          "lindex",            "linsert",
    "llen",              "lpop",              "lpush",
    "lpushx",            "lrange",            "lrem",
    "lset",              "ltrim",             "mget",
    "monitor",           "move",              "mset",
    "msetnx",            "multi",             "object",
    "persist",           "ping",              "psubscribe",
    "publish",           "punsubscribe",      "quit",
    "randomkey",         "rename",            "renamenx",
    "rpop",              "rpoplpush",         "rpush",
    "rpushx",            "sadd",              "save",
    "scard",             "script",
    "sdiff",             "sdiffstore",
    "select",            "set",               "setbit",
    "setex",             "setnx",             "setrange",
    "shutdown",          "sinter",            "sinterstore",
    "sismember",         "slaveof",           "slowlog",
    "smembers",          "smove",             "sort",
    "spop",              "srandmember",       "srem",
    "strlen",            "subscribe",         "sunion",
    "sunionstore",       "sync",              "ttl",
    "type",              "unsubscribe",       "unwatch",
    "watch",             "zadd",              "zcard",
    "zcount",            "zincrby",           "zinterstore",
    "zrange",            "zrangebyscore",     "zrank",
    "zrem",              "zremrangebyrank",   "zremrangebyscore",
    "zrevrange",         "zrevrangebyscore",  "zrevrank",
    "zscore",            "zunionstore",       "evalsha"
}



local  u_info  = "u_info_%d"
local  u_idols = "u_idols_%d"
local  u_weibo = "u_weibo_%d"

local  u_tres_merge_usr = "u_tres_merge_usr_%d"
local  u_tres_weibo = "u_tres_weibo_%d"

local  g_posted_weibo_usr = "g_posted_weibo_usr"
local  g_disabled_usr  = "g_disabled_usr"
local  g_online_usr  = "g_online_usr"
local  g_posted_notify_usr  = "g_posted_notify_usr"
  

local  u_online_fans = "u_online_fans_%d"
local  u_at_me       = "u_at_me_%d"
local  u_msg         = "u_msg_%d"

local  e_weibo       = "e_weibo_%s"

local  page_size    = 30
local  max_page     = 100
local  max_expire_day_sec = 1296000  --15*(60*60*24) 

----------------------------------------util-----------------------------------

for i, cmd in base.ipairs(commands) do
    weibo_redis[cmd] =
        function (...)
            return pcall(cmd, ...)
        end
end

local function table_tostring(t)
 local res = "{"
 for k,v in base.pairs(t) do
  res = string.format("%s [\"%s\"]=\"%s\", ",res,k,v)
 end
 res = string.format("%s }",res)
 return res
end


local function print_table(t)
 local res = ""
 for k,v in base.pairs(t) do
  res = string.format("%s %s=%s, ",res,k,v)
 end
 return res
end


local function in_array_pos2(a,e)
  local n = table.getn(a)
  for i = 2,n,2 do
     local v = a[i]
     if v <= e then return i end
  end
  return n
end

local function in_array_pos(a,e)
  local n = table.getn(a)
  for i = 1,n do
     local v = a[i]
     if v <= e then return i end
  end
  return n
end

local function array_limit(list,start,len)
  local res = {}
  local n   = table.getn(list)
  local e   = start + len 
        e   = ((e>n) and n) or e
  for i=start,e do
        res[i] = list[i]
  end
  return res
end

local function union2 (a,b)
  local idx = 1
  local n   = table.getn(a)
  res  = {}
  local residx = 1
  for i = 1,n,2 do
     local av = a[i]
     local bv = b[idx]
     if bv > av then
      for ii = idx,#b,2 do
       if b[ii] > av then
         res[residx]   = b[ii]
         res[residx+1] = b[ii+1]
         residx = residx + 2
         idx    = idx + 2
       else
         break
       end
      end
     end

     res[residx] = av
     res[residx+1] = a[i+1]
     residx = residx + 2
  end

  for ii = idx,#b,2 do
    res[residx]   = b[ii]
    res[residx+1] = b[ii+1]
    residx = residx + 2
  end

  return res
end



local function union (a,b)
  local idx = 1
  local n   = table.getn(a)
  res  = {}
  local residx = 1
  for i = 1,n do
     local av = a[i]
     local bv = b[idx]
     redis.log(redis.LOG_DEBUG,"union av:",av," bv:",bv)
     if bv > av then
      for ii = idx,#b do
       if b[ii] > av then
         res[residx]   = b[ii]
         --res[residx+1] = b[ii+1]
         residx = residx + 1 
         idx    = idx + 1
       else
         break
       end
      end
     end

     res[residx] = av
     --res[residx+1] = a[i+1]
     residx = residx + 1
  end

  for ii = idx,#b do
    res[residx]   = b[ii]
    --res[residx+1] = b[ii+1]
    residx = residx + 1
  end

  return res
end



local function _add(a,e)
  redis.log(redis.LOG_DEBUG,"e.wid:",e.wid) 
  a[e.wid] = e
  return a
end

local function _split(str,delim,_t)
  local i,j,k  
  local t = ((_t) and _t) or {}  
  k = 1  
  while true do  
     i,j = base.string.find(str,delim,k) 
     if i == nil then  
        base.table.insert(t,base.string.sub(str,k))  
        return t  
     end  
     base.table.insert(t,base.string.sub(str,k,i - 1))  
     k = j + 1  
  end  
end  

--[[

String UserIdolsKey(long uid);
int doInitIdols(ShardedJedis jedis ,long uid);
int initIdols(long uid);

List<Long> getIdols(long uid);
int idol(long uid,long auid);
int cancelIdol(long uid,long auid); 

--]]

-----------------------------------------------------idol-----------------------------------------------------

function idol(uid , tuid)

   if not uid or not tuid then 
      redis.log(redis.LOG_WARNING,"#idol invalid param") 
      return -1 
   end
  
   redis.log(redis.LOG_DEBUG,"#idol arg uid:",uid," tuid:",tuid);
  
   local now   = os.time()
   local u_idols_key = string.format(u_idols,uid)
   local u_online_fans_key = string.format(u_online_fans,tuid)
   local u_tres_weibo_key = string.format(u_tres_weibo,uid) 

   local res = zadd(u_idols_key,now,tuid)
   redis.log(redis.LOG_DEBUG,"#idol uid:",uid,
             " idoled tuid:",tuid," res:",res)

   zadd(u_online_fans_key,now,uid);
   redis.log(redis.LOG_DEBUG,"#idol add uid:" ,uid,
             " to tuid:",tuid,"`s u_online_fans")
   
   
   del(u_tres_weibo_key)
  
   return res
end

function myidols(uid,start,len)

   if not uid then 
      redis.log(redis.LOG_WARNING,"#myidols invalid param") 
      return -1 
   end
  
   redis.log(redis.LOG_DEBUG,"#myidols arg uid:",uid)
  
   local u_idols_key = string.format(u_idols,uid)

   return zrevrangebyscore(u_idols_key,"+inf","-inf","LIMIT",start,len)
end


function cancelIdol(uid,tuid)

   if not uid or not tuid then 
      redis.log(redis.LOG_WARNING,"#cancelIdol invalid param") 
      return -1 
   end
  
   redis.log(redis.LOG_DEBUG,"#cancelIdol arg uid:",uid," tuid:",tuid);
  
   local now   = os.time()
   local u_idols_key = string.format(u_idols,uid)
   local u_online_fans_key = string.format(u_online_fans,tuid)
   local u_tres_weibo_key = string.format(u_tres_weibo,uid) 

   local res = zrem(u_idols_key,tuid)
   redis.log(redis.LOG_DEBUG,"#cancelIdol  uid:",uid
             ," cancelIdoled tuid:",tuid," res:",res)

   zrem(u_online_fans_key,uid);
   redis.log(redis.LOG_DEBUG,"#cancelIdol  remove uid:" ,
             uid," to tuid:",tuid,"`s u_online_fans")


   del(u_tres_weibo_key)
  
   return res
end

function getIdols(uid)
   local u_idols_key = string.format(u_idols,uid)
   return zrange(u_idols_key,0,-1)
end


function disableUsr(uid)
   local u_info_key = string.format(u_info,uid)
   local u_weibo_key = string.format(u_weibo,uid)

   local now = os.time()
   local max_day_expire_point = now - max_expire_day_sec -- 15 day before second point 

   hset(u_info_key,"disabled",1) --0:init 1:editor deleted
  
   local wids = zrevrangebyscore(u_weibo_key,now,max_day_expire_point)
 
   for i,wid in base.ipairs(wids) do
     local e_weibo_key = string.format(e_weibo,wid)
     if hsetnx(e_weibo_key,"disabled",1) then -- 0:init 1:dis 2:del
     else
     end
   end
    

   return 0
end

function enableUsr(uid)
   local u_info_key = string.format(u_info,uid)
   local u_weibo_key = string.format(u_weibo,uid)

   local now = os.time()
   local max_day_expire_point = now - max_expire_day_sec -- 15 day before second point 

   hset(u_info_key,"disabled",1) --0:init 1:editor deleted
  
   local wids = zrevrangebyscore(u_weibo_key,now,max_day_expire_point)
 
   for i,wid in base.ipairs(wids) do
     local e_weibo_key = string.format(e_weibo,wid)
     hset(e_weibo_key,"disabled",1) -- 0:init 1:dis 2:del
   end
    

   return 0
end

-----------------------------------------------news-------------------------------------------------------------
--[[ 
int publish(News news);
int del(long uid,long id);	 

long getNidByUTT(long uid,long tid,byte type);

int changeState(long tid,byte state);

News  get(String id);
List<News> gets(Collection<String> nids);

int   getNewsCnt(long uid,byte type);

int  getSelfNewsCnt(long uid,byte type);

List<News> idolNews(long uid,int start,int len);
List<News> selfNews(long uid,int start,int len);
List<News> idolNews(long uid,long nid,int len);
--]]


local function _updateUsrNewWeibo(uid,wid,now)
   local u_weibo_key = string.format(u_weibo,uid)
   local u_info_key = string.format(u_info,uid)
   
   --1: update usr history weibo
   zadd(u_weibo_key,now,wid) 

   --2: update usr last weibo 
   hset(u_info_key,"lastwid",wid)
end

local function _saveWeibo(args)
   local wid  = args['wid']
   if not wid or wid == -1 then return -1 end

   local e_weibo_key = string.format(e_weibo,wid)
   
   for k,v in base.pairs(args) do 
      hset(e_weibo_key,k,v)
   end
end

local function _notifyUsrFansNewWeibo(uid)
   lpush(g_posted_notify_usr,uid)
end



function publish(args)

   local uid  = args['uid']
   local wid  = args['wid']
   local cont = args['cont']
   local src  = args['src']
   local _type = args['type']   -- 0:weibo 1:forward  
   local tid = args['tid']   -- 0:weibo 1:forward  
   
   local now = os.time()
   local max_day_expire_point = now - max_expire_day_sec -- 15 day before second point 

   if not uid or not wid  then
      redis.log(redis.LOG_WARNING,"#publish invalid param") 
      return -1 
   end

   local g_posted_weibo_usr_key = g_posted_weibo_usr

   local u_online_fans_key = string.format(u_online_fans,uid)

   --local u_msg 

   redis.log(redis.LOG_DEBUG,"#publish  args:" ,print_table(args))

   --1: save weibo
   _saveWeibo(args)

   --2: update user`s weibo state 
   _updateUsrNewWeibo(uid,wid,now)

   --3: update global "g_posted_weibo_usr" 
   zadd(g_posted_weibo_usr_key,now,uid)
   local delcnt = zremrangebyscore(g_posted_weibo_usr_key,"-inf",max_day_expire_point)
   redis.log(redis.LOG_DEBUG,"del exaccess max_day_expire_point g_posted_weibo_usr_key cnt:",delcnt)

   --4: notify online fans for msg
   _notifyUsrFansNewWeibo(uid)


   --5: keep max_btimewid
   local max_btimewid_key = os.date("%Y-%m-%d")
   if exists(max_btimewid_key) then 
      setex(max_btimewid_key,(60*60*24),wid) 
   end
     
   return 0 
end


function delWeibo(uid,wid)
   local e_weibo_key = string.format(e_weibo,wid)
   local u_weibo_key = string.format(u_weibo,uid)

   local score = zscore(u_weibo_key,wid)
  
   if not score then return -2 end
   
   --1: change weibe state 
   hset(e_weibo_key,"disabled",2) -- 0:init 1:del 2:dis

   return 0 
end


function getWeibo(wid)
   local e_weibo_key = base.string.format(e_weibo,wid)
   local ret = hgetall(e_weibo_key) 
   return ret
end



function getWeiboes(list)
   local wids = {}
   local res = {}
   local mt  = {__add=_add}
   --base.setmetatable(res,mt)

   redis.log(redis.LOG_DEBUG,"#getWeiboes param:",list)

   _split(list,",",wids)

   for i,v in base.ipairs(wids) do 
      local weibo = getWeibo(v) 
      table.insert(res,weibo)
   end
 
   return res
end


local function _appendNewWidToTempKey(uid,cached_newest_wid_and_time)

   local u_idols_key      = string.format(u_idols,uid)
   local g_posted_weibo_usr_key = g_posted_weibo_usr
   local u_tres_merge_usr_key = string.format(u_tres_merge_usr,uid) 
   local u_tres_weibo_key = string.format(u_tres_weibo,uid) 

   local now = os.time()
   local max_day_expire_point = now - max_expire_day_sec -- 15 day before second point 
   --[[local _cached_newest_wid_and_time = ((not cached_newest_wid_and_time ) and 
                                         zrevrange(u_tres_weibo_key,0,0,"WITHSCORES") ) 
                                        or cached_newest_wid_and_time 

   redis.log(redis.LOG_DEBUG,"_cached_newest_wid_and_time wid:",_cached_newest_wid_and_time[1],
                             "time:",_cached_newest_wid_and_time[2])

   if not _cached_newest_wid_and_time then 
      redis.log(redis.LOG_DEBUG,"#_appendNewWidToTempKey _cached_newest_wid_and_time empty")
      return 0
   end --]]

   local max_btime = ((not cached_newest_wid_and_time[2]) and 0 ) or tonumber(cached_newest_wid_and_time[2])
         max_btime = ((max_btime > max_day_expire_point) and max_btime ) or  max_day_expire_point

   --1: combine the usr idols and g_posted_weibo_usr 
   local cnt = zinterstore(u_tres_merge_usr_key,2,u_idols_key,
                  g_posted_weibo_usr_key,"AGGREGATE","MAX")

   if cnt <= 0 then 
      redis.log(redis.LOG_DEBUG,"cnt:",cnt)
      return 0 
   end

    --2: compute weibo
   local mergeUsr = zrevrangebyscore(u_tres_merge_usr_key,now,max_btime,"WITHSCORES")
   local allsize  = table.getn(mergeUsr)
   local usrcnt   = allsize / 2
   local btime = max_btime
   local etime = now 

   if cached_newest_wid_and_time[2] then 
      local lastUsrWidTime = mergeUsr[2]
      if cached_newest_wid_and_time[2] == lastUsrWidTime then
         return zcount(u_tres_weibo_key)
      end
   end


   
   ---local list = {}
   --local mt   = {__add=union}
   --setmetatable(list,mt)
   for i=1,allsize,2 do

     local muid  = mergeUsr[i]
     local u_weibo_key = string.format(u_weibo,muid)
     local tlist = zrevrangebyscore(u_weibo_key,etime,btime,"WITHSCORES")
     cnt  = cnt + table.getn(tlist)
     --setmetatable(tlist,mt)
     ---list = list + tlist
     for ii=1,#tlist,2 do
        zadd(u_tres_weibo_key,tlist[ii+1],tlist[ii])
     end
     redis.log(redis.LOG_DEBUG,"tlist:",#tlist,"key:",u_weibo_key,"etime:",etime,"btime:",btime)

   end

   local delcnt = zremrangebyscore(u_tres_weibo_key,"-inf",max_day_expire_point)
   redis.log(redis.LOG_DEBUG,"trim ",u_tres_weibo_key," delcnt:",delcnt)

   return cnt
end

function idolWeiboCnt(uid)
   local u_tres_weibo_key = string.format(u_tres_weibo,uid) 

   if not exists(u_tres_weibo_key) then 
      local cnt = _appendNewWidToTempKey(uid)
      return cnt
   end 

   --[[
   local now = os.time()
   local max_day_expire_point = now - max_expire_day_sec -- 15 day before second point 
   return zcount(u_tres_weibo_key,max_day_expire_point,"+inf")
   --]]
 
   return zcard(u_tres_weibo_key) 
end


function idolWeiboByWid(uid,wid,len)
   local u_tres_weibo_key = string.format(u_tres_weibo,uid) 

   if not exists(u_tres_weibo_key) then 
      local cnt = _appendNewWidToTempKey(uid)
      if not cnt then return {} end
   end 

   local rank = zrank(u_tres_weibo_key,wid)

   if rank > 0 then
     return zrange(u_tres_weibo_key,rank,(rank+len))
   end

   return {}
end

function idolWeibo(uid,start,len)
   local u_tres_weibo_key = string.format(u_tres_weibo,uid) 

   local cached_newest_wid_and_time = zrevrange(u_tres_weibo_key,0,0,"WITHSCORES")

   if cached_newest_wid_and_time then
     _appendNewWidToTempKey(uid,cached_newest_wid_and_time) 
     return zrevrangebyscore(u_tres_weibo_key,"+inf","-inf","LIMIT",start,len) 
   end

   
   local cnt = _appendNewWidToTempKey(uid) 
   if not cnt then
      redis.log(redis.LOG_DEBUG,"idolWeibo empty cnt:",cnt)
      return {}
   end

   return zrevrangebyscore(u_tres_weibo_key,"+inf","-inf","LIMIT",start,len) 
end


function selfWeiboCnt(uid)
   local u_weibo_key = string.format(u_weibo,uid)

   return zcard(u_weibo_key)
end

function selfWeibo(uid,start,len)
   local u_weibo_key = string.format(u_weibo,uid)
   
   return zrangebyscore(u_weibo_key,"-inf","+inf","limit",start,len) 
end
